package com.franklin.java.action.sql;

import com.flychord.jdk.util.matchcase.TupleCase;
import com.flychord.jdk.util.tuple.Tuple2;
import com.flychord.jdk.util.tuple.Tuple3;
import com.franklin.java.api.find.MyFindManager;
import com.franklin.java.completion.map.MapData;
import com.franklin.java.completion.map.mybatis.MapperMethodHandler;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.utils.PsiUtils;
import com.franklin.plugins.common.collection.PrettyArrayList;
import com.franklin.plugins.common.collection.PrettyHashSet;
import com.franklin.plugins.common.utils.DataCheckUtils;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.tree.java.PsiJavaTokenImpl;
import com.intellij.psi.util.PsiTreeUtil;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;

/**
 * 修复Sql
 *
 * @author Franklin
 * @since 2021/9/6 17:27
 */
public class MapColumnFixAction extends AbstractSqlAction {

    private Map<String, MapData> oldColumnsCache = Collections.emptyMap();

    protected AtomicInteger version = new AtomicInteger(0);

    private BlockingQueue<Tuple2<PsiElement, String>> changeQueue;

    public MapColumnFixAction(PsiElement sqlActionElement) {
        super(
            "Fix map columns you had changed",
            "If you change your column names , use this to quick fix",
            AllIcons.General.Settings,
            sqlActionElement
        );
    }


    @Override
    public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
        try {
            PsiMethod mapperMethod = getMapperMethod();
            if (Objects.isNull(mapperMethod)) {
                return;
            }
            Project project = mapperMethod.getProject();
            PsiType returnType = mapperMethod.getReturnType();
            if (Objects.isNull(returnType)) {
                return;
            }
            String typeText = StringUtils.deleteWhitespace(returnType.getPresentableText());
            if (!(PsiUtils.isListMap(typeText) || PsiUtils.isJavaMap(project, returnType.getCanonicalText()))) {
                return;
            }


            AtomicInteger beforeSize = new AtomicInteger();
            AtomicInteger sameTime = new AtomicInteger();
            // TODO: 2021/9/7 监听成功
            List<PsiElement> usageList = MyFindManager.getInstance(project)
                .findUsages(mapperMethod);


            while (true) {
                if (beforeSize.get() == usageList.size()) {
                    if (beforeSize.get() == 0) {
                        sleep(500);
                    } else {
                        sameTime.incrementAndGet();
                        sleep(100);
                        if (sameTime.get() == 5) {
                            break;
                        }
                    }
                } else {
                    beforeSize.set(usageList.size());
                    sameTime = new AtomicInteger();
                }
            }

            if (DataCheckUtils.isNotEmpty(usageList)) {
                //解析SQL
                Map<String, MapData> nowSqlResult = resolveSql();
                changeQueue = new LinkedBlockingQueue<>();
                for (PsiElement psiElement : usageList) {
                    handleUsage(psiElement, nowSqlResult);
                }

                if (DataCheckUtils.isNotEmpty(changeQueue)) {
                    WriteCommandAction.runWriteCommandAction(
                        sqlActionElement.getProject(),
                        () -> changeQueue.forEach(
                            TupleCase.match(
                                (oldKeyElement, newKey) -> {
                                    String oldKey = oldKeyElement.getText();
                                    int startOffset = PsiUtils.findStartOffsetInPsiFile(oldKeyElement);
                                    PsiFile containingFile = oldKeyElement.getContainingFile();
                                    Optional.ofNullable(containingFile.getViewProvider().getDocument())
                                        .ifPresent(document -> {
                                            document.replaceString(startOffset, startOffset + oldKey.length(), newKey);
                                            PsiDocumentManager.getInstance(project).commitDocument(document);
                                        });
                                }
                            )
                        )
                    );
                }
            }
        } finally {
            oldColumnsCache = Collections.emptyMap();
            //重置版本号
            version = new AtomicInteger(0);
            changeQueue = null;
        }

    }

    private void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    Map<String, MapData> storeOldSqlResult() {
        if (version.incrementAndGet() == 1) {
            oldColumnsCache = resolveSql();
        }
        return oldColumnsCache;
    }


    private void handleUsage(PsiElement psiElement, Map<String, MapData> nowSqlResult) {
        PsiMethod psiMethod = PsiTreeUtil.getParentOfType(psiElement, PsiMethod.class);
        if (Objects.isNull(psiMethod)) {
            // TODO: 2021/9/7 处理代码块？？？
            return;
        }
        //直接遍历
        Optional.ofNullable(PsiTreeUtil.getParentOfType(psiElement, PsiForeachStatement.class))
            .ifPresent(psiForeachStatement -> this.handleForeachLoop(psiElement, psiForeachStatement, nowSqlResult));

        //直接forEach 或者 stream api
        Optional.ofNullable(PsiUtils.findFullMethodCallExpression(psiElement))
            .ifPresent(psiMethodCallExpression -> this.handleMethodCallExpression(psiElement, psiMethodCallExpression, nowSqlResult));

        //声明了变量
        Optional.ofNullable(PsiTreeUtil.getParentOfType(psiElement, PsiDeclarationStatement.class))
            .ifPresent(psiDeclarationStatement -> this.handleDeclaration(psiDeclarationStatement, psiMethod, nowSqlResult));

    }

    /**
     * for each 循环<br>
     *
     * @param psiForeachStatement :
     * @param nowSqlResult
     * @return void
     * @author Franklin
     * @since 2021/9/7 17:05
     */
    private void handleForeachLoop(PsiElement psiElement, PsiForeachStatement psiForeachStatement, Map<String, MapData> nowSqlResult) {
        PsiExpression iteratedValue = psiForeachStatement.getIteratedValue();
        PsiStatement body = psiForeachStatement.getBody();
        if (Objects.isNull(iteratedValue)) {
            return;
        }
        if (Objects.isNull(body)){
            return;
        }
        if (iteratedValue.getText().contains(psiElement.getText())) {
            //直接迭代
            PsiParameter iterationParameter = psiForeachStatement.getIterationParameter();
            findGetMethod(nowSqlResult, body, iterationParameter.getName());
        }
    }

    /**
     * 寻找get方法，修改get的key<br>
     *
     * @param nowSqlResult:
     * @param body:
     * @param paramName:
     * @return void
     * @author Franklin
     * @since 2021/9/8 15:57
     */
    private void findGetMethod(Map<String, MapData> nowSqlResult, PsiElement body, String paramName) {
        String[] lines = body.getText().split("\n");
        String target = paramName + ".get(";
        int length = 0;
        Map<String, MapData> oldColumnMap = oldColumnsCache;
        for (String line : lines) {
            List<Integer> keyIndexList = findKey(line);
            for (Integer index : keyIndexList) {
                PsiElement oldKeyElement = body.findElementAt(index + length);
                if (Objects.nonNull(oldKeyElement)) {
                    String key = oldKeyElement.getText();
                    MapData oldData = oldColumnMap.get(key);
                    doChange(oldKeyElement, oldData, nowSqlResult);
                }
            }

            length = length + line.length() + 1;
        }
    }

    /**
     * 修改key<br>
     *
     * @param oldKeyElement:
     * @param oldData:
     * @param nowSqlResult:
     * @return void
     * @author Franklin
     * @since 2021/9/8 15:57
     */
    private void doChange(PsiElement oldKeyElement, MapData oldData, Map<String, MapData> nowSqlResult) {
        String key = oldKeyElement.getText();
        if (nowSqlResult.containsKey(key)) {
            //没有变化
            return;
        }
        for (Map.Entry<String, MapData> entry : nowSqlResult.entrySet()) {
            MapData newData = entry.getValue();
            ColumnInfo newColumn = newData.getColumnInfo();
            String columnName = newColumn.getColumnName();
            ColumnInfo oldColumn = oldData.getColumnInfo();
            String columnNameOld = oldColumn.getColumnName();
            if (columnName.equals(columnNameOld)) {
                //key发生变化了 ，修改Key
                changeQueue.add(tuple(oldKeyElement, entry.getKey()));
                return;
            }else if (oldColumn.getColumnIndex().equals(newColumn.getColumnIndex())){
                if (oldColumn.getColumnSum().equals(newColumn.getColumnSum())) {
                    //行数没变,同行改变
                    changeQueue.add(tuple(oldKeyElement, entry.getKey()));
                    return;
                }
            }
        }
        //todo 提示字段删除

    }

    private List<Integer> findKey(String line) {
        Set<String> keys = oldColumnsCache.keySet();
        List<Integer> indexList = new PrettyArrayList<>();
        for (String key : keys) {
            int tempIndex = 0;
            int fromIndex = 0;
            while ((tempIndex = line.indexOf(key, fromIndex)) >= 0) {
                indexList.add(tempIndex);
                fromIndex = tempIndex + key.length();
            }
        }
        return indexList;
    }

    private void handleDeclaration(PsiDeclarationStatement psiDeclarationStatement, PsiMethod psiMethod, Map<String, MapData> nowSqlResult) {
        PsiLocalVariable psiLocalVariable = PsiTreeUtil.getChildOfType(psiDeclarationStatement, PsiLocalVariable.class);
        if (Objects.isNull(psiLocalVariable)) {
            return;
        }
        Project project = psiDeclarationStatement.getProject();
        String collectionName = psiLocalVariable.getName();
        PsiElement collectionElement = psiDeclarationStatement.findElementAt(psiDeclarationStatement.getText().indexOf(collectionName));
        String collectionType = psiLocalVariable.getType().getCanonicalText();
        PsiCodeBlock body = psiMethod.getBody();
        if (Objects.nonNull(body)) {
            if (PsiUtils.isJavaMap(project, collectionType)) {
                //直接处理get方法
                findGetMethod(nowSqlResult, body, collectionName);
            } else {
                //寻找for each 以及 lambda表达式
                String[] lines = body.getText().split("\n");
                Set<PsiElement> handledSet = new PrettyHashSet<>();
                int length = 0;
                for (String line : lines) {
                    PsiElement psiElement = body.findElementAt(length + line.indexOf("for"));
                    PsiForeachStatement psiForeachStatement = PsiTreeUtil.getParentOfType(psiElement, PsiForeachStatement.class);
                    if (Objects.nonNull(psiForeachStatement)) {
                        if (!handledSet.contains(psiForeachStatement)) {
                            handledSet.add(psiForeachStatement);
                            handleForeachLoop(collectionElement, psiForeachStatement, nowSqlResult);
                            length = length + psiForeachStatement.getText().length() + 1;
                        }
                    } else {
                        //lambda表达式
                        psiElement = body.findElementAt(length + line.indexOf("->"));
                        PsiLambdaExpression psiLambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
                        if (Objects.nonNull(psiLambdaExpression)) {
                            PsiMethodCallExpression fullMethodCallExpression = PsiUtils.findFullMethodCallExpression(psiLambdaExpression);
                            if (Objects.nonNull(fullMethodCallExpression)) {
                                if (!handledSet.contains(fullMethodCallExpression)) {
                                    handledSet.add(fullMethodCallExpression);
                                    handleMethodCallExpression(collectionElement, fullMethodCallExpression, nowSqlResult);
                                    length = length + fullMethodCallExpression.getText().length() + 1;
                                }
                            }
                        } else {
                            length = length + line.length() + 1;
                        }
                    }
                }
            }
        }
    }

    private void handleMethodCallExpression(PsiElement collectionElement, PsiMethodCallExpression psiMethodCallExpression, Map<String, MapData> nowSqlResult) {
        String text = psiMethodCallExpression.getText();
        String collectionName = collectionElement.getText();
        if (!text.contains(collectionName)) {
            return;
        }
        int index = 0;
        int fromIndex = 0;
        Project project = collectionElement.getProject();
        while ((index = text.indexOf("->", fromIndex)) >= 0) {
            PsiElement psiElement = psiMethodCallExpression.findElementAt(index);
            PsiLambdaExpression psiLambdaExpression = PsiTreeUtil.getParentOfType(psiElement, PsiLambdaExpression.class);
            if (Objects.nonNull(psiLambdaExpression)) {
                PsiParameter[] psiParameters = psiLambdaExpression.getParameterList().getParameters();
                for (PsiParameter psiParameter : psiParameters) {
                    PsiType psiType = psiParameter.getType();
                    String type = psiType.getCanonicalText();
                    if (PsiUtils.isJavaMap(project, type)) {
                        PsiElement body = psiLambdaExpression.getBody();
                        if (Objects.nonNull(body)) {
                            findGetMethod(nowSqlResult, body, psiParameter.getName());
                        }
                    }
                }

                fromIndex = index + psiLambdaExpression.getText().length();
            } else {
                fromIndex = index + 2;
            }
        }
    }
}
